package com.kgcx.common.swagger;

import com.kgcx.core.utils.JsonUtil;
import io.swagger.models.*;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.service.Documentation;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl;

import java.util.List;
import java.util.Map;

/**
 * @author 袁进勇
 */
@Primary
@Component
@ConditionalOnProperty(prefix = "kgcx.swagger", name = "enable", havingValue = "true")
public class CustServiceModelToSwagger2MapperImpl extends ServiceModelToSwagger2MapperImpl {
    @Override
    public Swagger mapDocumentation(Documentation from) {
        Swagger swagger = super.mapDocumentation(from);
        if (swagger == null) {
            return null;
        }

        Map<String, Path> paths = swagger.getPaths();
        for (Map.Entry<String, Path> pathEntry : paths.entrySet()) {
            Path path = pathEntry.getValue();
            processOperation(swagger, path.getPost());
        }

        return swagger;
    }

    private void processOperation(Swagger swagger, Operation operation) {
        if (operation == null) {
            return;
        }

        List<Parameter> parameters = operation.getParameters();
        if (parameters == null || parameters.size() == 0) {
            return;
        }

        for (Parameter parameter : parameters) {
            if (!(parameter instanceof BodyParameter)) {
                continue;
            }

            BodyParameter bodyParameter = (BodyParameter) parameter;
            Model schema = bodyParameter.getSchema();
            if (schema instanceof RefModel) {
                RefModel refModel = (RefModel) schema;
                refModel.asDefault(processRefModel(swagger, operation, bodyParameter, refModel));
            }
        }
    }

    private String processRefModel(Swagger swagger, Operation operation, BodyParameter parameter, RefModel refModel) {
        Model model = swagger.getDefinitions().get(refModel.getSimpleRef());
        Map<String, Property> properties = model.getProperties();
        if (properties == null || properties.size() == 0) {
            return refModel.getSimpleRef();
        }

        String ref = refModel.getSimpleRef() + "$" + operation.getOperationId();
        swagger.getDefinitions().put(ref, processModel(swagger, operation, parameter, model));

        return ref;
    }

    private String processRefProperty(Swagger swagger, Operation operation, BodyParameter parameter,
                                      RefProperty refProperty) {
        Model model = swagger.getDefinitions().get(refProperty.getSimpleRef());
        Map<String, Property> properties = model.getProperties();
        if (properties == null || properties.size() == 0) {
            return refProperty.getSimpleRef();
        }

        String ref = refProperty.getSimpleRef() + "$" + operation.getOperationId();
        swagger.getDefinitions().put(ref, processModel(swagger, operation, parameter, model));

        return ref;
    }

    private Model processModel(Swagger swagger, Operation operation, BodyParameter parameter, Model model) {
        Model newModel = (Model) model.clone();
        for (Map.Entry<String, Property> propertyEntry : model.getProperties().entrySet()) {
            String propertyName = propertyEntry.getKey();
            Property property = propertyEntry.getValue();
            String operationId = operation.getOperationId();
            if (property.getReadOnly() != null && property.getReadOnly()) {
                if (!(operationId.startsWith("update") && "kid".equals(propertyName))) {
                    newModel.getProperties().remove(propertyName);

                    continue;
                }
            }


            if ("ref".equals(property.getType())) {
                RefProperty refProperty = (RefProperty) property;
                String ref = processRefProperty(swagger, operation, parameter, refProperty);
                if (ref.equals(refProperty.getSimpleRef())) {
                    continue;
                }

                RefProperty newRefProperty = JsonUtil.toObject(JsonUtil.toString(refProperty), RefProperty.class);
                newRefProperty.asDefault(ref);

                newModel.getProperties().put(propertyName, newRefProperty);
                continue;
            }

            if ("array".equals(property.getType())) {
                ArrayProperty arrayProperty = (ArrayProperty) property;
                Property items = arrayProperty.getItems();
                if ("ref".equals(items.getType())) {
                    RefProperty refProperty = (RefProperty) items;
                    String ref = processRefProperty(swagger, operation, parameter, refProperty);
                    if (ref.equals(refProperty.getSimpleRef())) {
                        continue;
                    }

                    RefProperty newRefProperty = JsonUtil.toObject(JsonUtil.toString(refProperty), RefProperty.class);
                    newRefProperty.asDefault(ref);

                    arrayProperty.setItems(null);

                    ArrayProperty newArrayProperty = JsonUtil.toObject(JsonUtil.toString(arrayProperty),
                            ArrayProperty.class);
                    newModel.getProperties().put(propertyName, newArrayProperty);
                    newArrayProperty.setItems(newRefProperty);

                    arrayProperty.setItems(items);
                    continue;
                }
            }
        }

        return newModel;
    }
}
